Fuzzy Search


Journaux liées à cette note :

Journal du samedi 21 décembre 2024 à 16:10 #iteration, #personal-knowledge-management, #search-engine, #ElasticSearch

Je viens d'améliorer l'implémentation du moteur de recherche de mon sklein-pkm-engine.

Voici un screencast de présentation du résultat :

Le commit de changement : https://github.com/stephane-klein/sklein-pkm-engine/commit/71210703fe626bd455b2ec7774167d9a637e4972

Je suis passé de :

query_string: {
    query: queryString,
    default_field: "content_html"
}

à ceci :

multi_match: {
	query: queryString,
	fields: ["title^2", "content_html"],
	fuzziness: "AUTO",
	type: "best_fields"
}

Les fonctionnalités de recherche d'Elasticsearch sont nombreuses. Pour les parcourir, je conseille ce point d'entrée de la documentation Search in Depth.
Même après avoir fini mon implémentation de la fonction recherche, je dois avouer que je tâtonne sur le sujet. Je suis loin de maitriser le sujet.

Au départ, après lecture de ce paragraphe :

If you don’t need to support a query syntax, consider using the match query. If you need the features of a query syntax, use the simple_query_string query, which is less strict.

source

J'ai fait un refactoring de query_string vers simple_query_string (lien vers la documentation).

Mon objectif était d'arriver à implémenter la fonctionnalité Query-Time Search-as-You-Type avec de la recherche floue (fuzzy).

J'ai commencé par essayer la syntax foobar~* mais j'ai appris qu'il n'était pas possible d'utiliser ~ (fuzzy) en couplé avec * 😔 (documentation vers la syntax). Sans doute pour de bonnes raisons, liées à des problèmes de performance.

J'ai ensuite découpé ma requête en 3 conditions :

baseQuery.body.query.bool.must.push({
	bool: {
		should: [
			{
				simple_query_string: {
					query: queryString,
					fields: ["content_html"],
					boost: 3
				}
			},
			{
				simple_query_string: {
					query: queryString.split(' ').map(word => (word.length >= 3) ? `${word}*` : undefined).join(' ').trim(),
					fields: ["content_html"],
					boost: 1
				}
			},
			{
				simple_query_string: {
					query: queryString.split(' ').map(
						word => {
							if (word.length >= 5) { return `${word}~2`; }
							else if (word.length >= 3) { return `${word}~1`; }
							else return undefined;
						}
					).join(' ').trim(),
					fields: ["content_html"],
					boost: 1
				}
			}
		],
		minimum_should_match: 1
	}
}

Cette implémentation fonctionne, mais je rencontrais des problèmes de performance aléatoires que je n'ai pas pris le temps d'essayer de comprendre la cause.

À force de tâtonnement, j'ai fini par choisir la solution basée sur multi_match (documentation de référence) :

multi_match: {
	query: queryString,
	fields: ["title^2", "content_html"],
	fuzziness: "AUTO",
	type: "best_fields"
}

Documentation de référence du paramètre fuzziness : Fuzzy query.

Documentation de la valeur AUTO : Common options - Fuzziness

Malheureusement, ici aussi, je ne peux pas utiliser fuzziness avec phrase_prefix :

The fuzziness parameter cannot be used with the phrase or phrase_prefix type.

source

En finissant cette note, je viens de découvrir cet exemple dans la documentation.

J'ai l'impression de comprendre qu'en utilisant le tokenizer ngram je pourrais faire des Fuzzy Search sans utiliser l'option fuzziness 🤔.

J'ai commencé l'implémentation dans la branche ngram-tokenizer mais je m'arrête là pour aujourd'hui. En tout, ce weekend, j'ai passé 4h30 sur ce sujet 😮.
J'espère tester cette implémentation d'ici à quelques jours.

Je souhaite aussi essayer prochainement de migrer de Elasticsearch vers OpenSearch.